package com.sunseaypw.db.trans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

public class NestTrans {
    public static void main(String[] args){
        ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("nestTrans.xml");
        var st=ctx.getBean("studentDAO",IDao.class);   

        //st.dbOperate1();
        st.dbOperate3();
        
    }   
    public static interface IDao{
        void dbOperate1();
        void dbOperate2();
        void dbOperate3();
        void dbOperate4();
    }
    /*  PROPAGATION_REQUIRED	如果当前没有事务，就新建一个事务，如果已存在一个事务中，加入到这个事务中，这是最常见的选择。
        PROPAGATION_SUPPORTS	支持当前事务，如果没有当前事务，就以非事务方法执行。
        PROPAGATION_MANDATORY	使用当前事务，如果没有当前事务，就抛出异常。
        PROPAGATION_REQUIRES_NEW	新建事务，如果当前存在事务，把当前事务挂起。
        PROPAGATION_NOT_SUPPORTED	以非事务方式执行操作，如果当前存在事务，就把当前事务挂起。
        PROPAGATION_NEVER	以非事务方式执行操作，如果当前事务存在则抛出异常。
        PROPAGATION_NESTED	如果当前存在事务，则在嵌套事务内执行。如果当前没有事务，则执行与PROPAGATION_REQUIRED 类似的操作

    */
    public static class StudentDAO implements IDao{          
        @Autowired
        JdbcTemplate jt;  
        
        /** 启动动态代理模式下，是接口模式的自我注入 解决同类内无法aop注入事务问题
         * <tx:annotation-driven transaction-manager="txManager" proxy-target-class="false" />
         */
        @Autowired
        IDao proxySelf;

        /** 启动cglib模式下，是子类继承模式的自我注入 解决同类内无法aop注入事务问题
         * <tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" />
         */
        // @Autowired
        // StudentDAO proxySelf;
        
        /*        
        */
        @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class )  
        public void dbOperate1(){            
            var con=DataSourceUtils.getConnection(jt.getDataSource());
            System.out.println(con);            
            jt.execute("update students set name='第111111111111111次更新' where id=10 ");
            jt.execute("update students set name='第222222222222222次更新' where id=10"); 
            try{
                proxySelf.dbOperate2(); 
            }catch (Exception ex) {  
                //完全隔离new事务的提交和失败              
            }                
            /* 切记，同一个类中的方法嵌套，无法激活spring aop注入的事务切面！！因为内部直接调用了target嵌套方法！
                解决方法：注入自身autowire再调用自身等
                        根本解决方法就是一个类中不要嵌套事务方法！！！！
            */            
        }
        /*1.REQUIRES_NEW会开启新事务，外层事务不会影响内层事务的提交和回滚操作
          2.REQUIRES_NEW如果内层事务出现异常，则外层事务也会同样进行回滚操作
        */
        @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class )     
        public void dbOperate2(){  
            var con=DataSourceUtils.getConnection(jt.getDataSource());
            System.out.println(con);          
            jt.execute("update students set name='第111111111111111次更新' where id=11 ");
            jt.execute("update students set name2222='第222222222222222次更新' where id=11");            
        }


        /*
        */
        @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class )     
        public void dbOperate3(){  
            var con=DataSourceUtils.getConnection(jt.getDataSource());
            System.out.println(con);   
            try {  
                proxySelf.dbOperate4();   
            } catch (Exception ex) {  
                //隔离nest事务的失败，但需要nest子事务跟自己一起commit                
            }    
            jt.execute("update students set name='第111111111111111次更新' where id=12 ");
            jt.execute("update students set name2222='第222222222222222次更新' where id=12");      
        }
        /* PROPAGATION_NESTED 开始一个 "嵌套的" 事务,  它是已经存在事务的一个真正的子事务. 
          潜套事务开始执行时,  它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 
          潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交,这个规则同样适用于 roll back。
          使用嵌套事务的场景有两点需求：
            需要事务BC与事务AD一起commit，即：作为事务AD的子事务，事务BC只有在事务AD成功commit时（阶段3成功）才commit。这个需求简单称之为“联合成功”。这一点PROPAGATION_NESTED和PROPAGATION_REQUIRED可以做到。
            需要事务BC的rollback不（无条件的）影响事务AD的commit。这个需求简单称之为“隔离失败”。这一点PROPAGATION_NESTED和PROPAGATION_REQUIRES_NEW可以做到。
            分解下，可知PROPAGATION_NESTED的特殊性有：
            1、使用PROPAGATION_REQUIRED满足需求1，但子事务BC的rollback会无条件地使父事务AD也rollback，不能满足需求2。即使对子事务进行了try-catch，父事务AD也不能commit。示例见2.4.1、trycatch+Propagation.REQUIRED+Propagation.REQUIRED
            2、使用PROPAGATION_REQUIRES_NEW满足需求2，但子事务（这时不应该称之为子事务）BC是完全新的事务上下文，父事务（这时也不应该称之为父事务）AD的成功与否完全不影响BC的提交，不能满足需求1。
            同时满足上述两条需求就要用到PROPAGATION_NESTED了。PROPAGATION_NESTED在事务AD执行到B点时，设置了savePoint（关键）。
            当BC事务成功commit时，PROPAGATION_NESTED的行为与PROPAGATION_REQUIRED一样。只有当事务AD在D点成功commit时，事务BC才真正commit，如果阶段3执行异常，导致事务AD rollback，事务BC也将一起rollback ，从而满足了“联合成功”。
            当阶段2执行异常，导致BC事务rollback时，因为设置了savePoint，AD事务可以选择与BC一起rollback或继续阶段3的执行并保留阶段1的执行结果，从而满足了“隔离失败”。
          必须要满足两个条件才能 createSavepoint 。
            1. java.sql.Savepoint 必须存在, 即 jdk 版本要 1.4+ 
            2. Connection.getMetaData().supportsSavepoints() 必须为 true, 即 jdbc drive 必须支持 JDBC 3.0 
            确保以上条件都满足后, 你就可以尝试使用 PROPAGATION_NESTED 了。
        */
        @Transactional(propagation = Propagation.NESTED,rollbackFor = Exception.class )     
        public void dbOperate4(){  
            var con=DataSourceUtils.getConnection(jt.getDataSource());
            System.out.println(con);          
            jt.execute("update students set name='第111111111111111次更新' where id=13 ");
            jt.execute("update students set name='第222222222222222次更新' where id=13");            
        }
    }     
}
